package com.moss.cloud.common.security.permission.impl;

import com.moss.cloud.auth.api.utils.JwtAccessToken;
import com.moss.cloud.common.core.constant.Constant;
import com.moss.cloud.common.core.model.po.PermissionMenu;
import com.moss.cloud.common.core.model.vo.AuthUser;
import com.moss.cloud.common.core.utils.AuthUserUtil;
import com.moss.cloud.common.security.component.RedisTemplateOps;
import com.moss.cloud.common.security.permission.IAuthService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.*;
/**
 * 业务鉴权接口实现
 * @author 瑾年
 * @date 2023年3月7日
 */
@Service
@Slf4j
public class AuthService implements IAuthService {

    private final RedisTemplateOps redisTemplateOps;


    public AuthService(RedisTemplateOps redisTemplateOps) {
        this.redisTemplateOps = redisTemplateOps;
    }

    @Override
    @SneakyThrows
    public boolean authenticate(String authentication, String url, String method) {
        return this.decide(authentication,url,method);
    }

    @Override
    public boolean hasPermission(String authentication, String url, String method) {
        //token是否有效
        if (JwtAccessToken.invalidJwtAccessToken(authentication)) {
            return Boolean.FALSE;
        }
        //从认证服务获取是否有权限
        return authenticate(authentication, url, method);
    }

    /**
     * 有权限true, 无权限或全局资源中未找到请求url返回否
     * @param url
     * @param method
     * @return 有权限true, 无权限或全局资源中未找到请求url返回否
     */
    public boolean decide(String authentication,String url,String method){
        log.info("正在访问的url是:{}，method:{}",url, method);
        //获取用户认证信息
        String userInfo = JwtAccessToken.getJwt(authentication).getClaims();
        AuthUser authUser = AuthUserUtil.preAuthUser(userInfo);
        log.info("用户认证信息:{}",authUser.toString());
        //获取此url，method访问对应的权限资源信息
        PermissionMenu permissionMenu = this.findSysMenu(url,method);
        if(Objects.isNull(permissionMenu)){
            log.info("url未在资源池中找到，拒绝访问");
            return Boolean.FALSE;
        }
        //获取此访问用户所有角色拥有的权限资源
        log.info("用户名:{}",authUser.getUserName());
        Set<PermissionMenu> userResources = findResourcesByUsername(authUser.getUserName());
        //用户拥有权限资源 与 url要求的资源进行对比
        return isMatch(permissionMenu, userResources);
    }
    /**
     * url对应资源与用户拥有资源进行匹配
     *
     * @param permissionMenu
     * @param userResources
     * @return
     */
    public boolean isMatch(PermissionMenu permissionMenu, Set<PermissionMenu> userResources) {
        return userResources.stream().anyMatch(resource -> resource.getAuth().equals(permissionMenu.getAuth()));
    }

    /**
     * 根据用户所被授予的角色，查询到用户所拥有的资源
     *
     * @param username
     * @return
     */
    private Set<PermissionMenu> findResourcesByUsername(String username) {
        //用户被授予的角色资源
        Set<PermissionMenu> permissionMenus = this.queryByUsername(username);
        if (log.isDebugEnabled()) {
            log.debug("用户被授予角色的资源数量是:{}, 资源集合信息为:{}", permissionMenus.size(), permissionMenus);
        }
        return permissionMenus;
    }

    /**
     * 从redis中获取资源池配置的当前访问的资源
     *
     * @param url
     * @param method
     * @return
     */
    private PermissionMenu findSysMenu(String url, String method) {
        Boolean hasKey = redisTemplateOps.hasKey(Constant.SYS_MENU_KEY + url + "_" + method);
        if (Boolean.TRUE.equals(hasKey)) {
            PermissionMenu permissionMenu = (PermissionMenu) redisTemplateOps.get(Constant.SYS_MENU_KEY + url + "_" + method);
            log.info("url在资源池中配置-资源名称->:{},资源路径:{}", permissionMenu.getName(), permissionMenu.getPath());
            return permissionMenu;
        }
        return new PermissionMenu();
    }

    /**
     * 根据用户名查找用户对应角色授权的资源信息
     *
     * @param username
     * @return
     */
    private Set<PermissionMenu> queryByUsername(String username) {
        Boolean hasKey = redisTemplateOps.hasKey(Constant.USER_SYS_MENU + username);
        if (Boolean.TRUE.equals(hasKey)) {
            List<PermissionMenu> permissionMenus = (List<PermissionMenu>) redisTemplateOps.get(Constant.USER_SYS_MENU + username);
            return new HashSet<>(permissionMenus);
        }
        return new HashSet<>(Collections.emptyList());
    }
}
